﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using CommunityToolkit.WinUI.Lottie.WinCompData;
using CommunityToolkit.WinUI.Lottie.WinCompData.MetaData;
using CommunityToolkit.WinUI.Lottie.WinCompData.Mgce;
using CommunityToolkit.WinUI.Lottie.WinCompData.Mgcg;
using CommunityToolkit.WinUI.Lottie.WinUIXamlMediaData;

namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen.Cppwinrt
{
    /// <summary>
    /// Generates code for use by CppWinrt.
    /// </summary>
#if PUBLIC_UIDataCodeGen
    public
#endif
    sealed class CppwinrtInstantiatorGenerator : InstantiatorGeneratorBase
    {
        const string Muxc = "winrt::Microsoft::UI::Xaml::Controls";
        readonly CppwinrtStringifier _s;
        readonly string _cppwinrtGeneratedFileNameBase;
        readonly string _headerFileName;
        readonly string _cppFileName;
        readonly string _idlFileName;

        // The name of the source class i.e. the class
        // that contains the TryCreateAnimatedVisual method.
        readonly string _sourceClassName;
        readonly string _wuc;
        readonly string _winNamespace;
        readonly string _winUINamespace;

        // The fully qualified name of the AnimatedVisual type that is returned
        // from the TryCreateAnimatedVisual method.
        readonly string _animatedVisualTypeName;

        readonly string _animatedVisualTypeName2;

        // True iff the generated code implements IDynamicAnimatedVisualSource.
        readonly bool _isIDynamic;

        /// <summary>
        /// Returns the Cppwinrt code for a factory that will instantiate the given <see cref="Visual"/> as a
        /// Windows.UI.Composition Visual.
        /// </summary>
        /// <returns>The result of the code generation.</returns>
        public static CppwinrtCodegenResult CreateFactoryCode(CodegenConfiguration configuration)
        {
            var generator = new CppwinrtInstantiatorGenerator(configuration, new CppwinrtStringifier());

            return new CppwinrtCodegenResult(
                cppFilename: generator._cppFileName,
                cppText: generator.GenerateCode(),
                hFilename: generator._headerFileName,
                hText: generator.GenerateHeaderText(),
                idlFilename: generator._idlFileName,
                idlText: generator.GenerateIdlText(),
                assets: generator.GetAssetsList()
            );
        }

        CppwinrtInstantiatorGenerator(CodegenConfiguration configuration, CppwinrtStringifier stringifier)
            : base(
                  configuration,
                  setCommentProperties: false,
                  stringifier)
        {
            _s = stringifier;

            // Cppwinrt.exe usually generates file names that match the fully-qualifed name
            // of the type that they are for, however the VS project template for apps that
            // use cppwinrt will parameterize cppwinrt.exe so that the "root namespace" is
            // stripped from the front of the file names. We match their behavior so that
            // the file names generated by us and by cppwinrt.exe are consistent.
            var rootNamespace = configuration.RootNamespace.TrimEnd('.');
            var cppwinrtFileNameBase = $"{SourceInfo.Namespace}.{SourceInfo.ClassName}";
            if (cppwinrtFileNameBase.StartsWith($"{rootNamespace}."))
            {
                // Strip off the root namespace. This is to match the file naming used
                // by cppwinrt.exe which strips the "root namespace" from file names in
                // VS projects.
                cppwinrtFileNameBase = cppwinrtFileNameBase.Substring(rootNamespace.Length + 1);
            }

            // The base of the name used for files generated by cppwinrt.
            _cppwinrtGeneratedFileNameBase = $"{cppwinrtFileNameBase}.g";

            _cppFileName = $"{cppwinrtFileNameBase}.cpp";
            _headerFileName = $"{cppwinrtFileNameBase}.h";
            _idlFileName = $"{cppwinrtFileNameBase}.idl";

            _isIDynamic = SourceInfo.LoadedImageSurfaces.Any();

            _winUINamespace = SourceInfo.WinUIVersion.Major >= 3 ? "Microsoft::UI" : "Windows::UI";
            _winNamespace = SourceInfo.WinUIVersion.Major >= 3 ? "Microsoft" : "Windows";
            _wuc = $"{_winUINamespace}::Composition";
            _sourceClassName = SourceInfo.ClassName;

            _animatedVisualTypeName = Interface_IAnimatedVisual.GetQualifiedName(_s);
            _animatedVisualTypeName2 = Interface_IAnimatedVisual2.GetQualifiedName(_s);
        }

        static string FieldAssignment(string fieldName) => fieldName is not null ? $"{fieldName} = " : string.Empty;

        IAnimatedVisualSourceInfo SourceInfo => AnimatedVisualSourceInfo;

        /// <summary>
        /// Generates the text for the .IDL file.
        /// </summary>
        /// <returns>The text for the .IDL file.</returns>
        string GenerateIdlText()
        {
            var builder = new CodeBuilder();
            builder.WriteLine(string.Join("\r\n", AutoGeneratedHeaderText));

            if (SourceInfo.AdditionalInterfaces.Any())
            {
                foreach (var import in SourceInfo.AdditionalInterfaces.Select(n => n.NormalizedQualifiedName))
                {
                    // Import an IDL file for the definition of each additional interface.
                    // We don't know whether the user defined the interface in IDL or
                    // whether they are reusing an interface that is already defined in
                    // a .winmd, so be resilient to both cases by only importing the IDL
                    // file if it exists.
                    WriteImportFileIfExists(builder, $"{import}.idl");
                    builder.WriteLine();
                }
            }

            builder.WriteLine($"namespace {SourceInfo.Namespace}");
            builder.OpenScope();
            builder.WriteLine($"runtimeclass {_sourceClassName}");
            builder.Indent();

            if (_isIDynamic)
            {
                builder.WriteLine($": [default] {Interface_IDynamicAnimatedVisualSource.NormalizedQualifiedName}");
            }
            else
            {
                builder.WriteLine($": [default] {Interface_IAnimatedVisualSource.NormalizedQualifiedName}");

                if (SourceInfo.WinUIVersion >= new Version(2, 6) && SourceInfo.WinUIVersion.Major < 3)
                {
                    builder.WriteLine($", {Interface_IAnimatedVisualSource2.NormalizedQualifiedName}");
                }
            }

            if (_isIDynamic)
            {
                builder.WriteLine($", {_winNamespace}.UI.Xaml.Data.INotifyPropertyChanged");
            }

            foreach (var additionalInterface in SourceInfo.AdditionalInterfaces.Select(n => n.NormalizedQualifiedName))
            {
                builder.WriteLine($", {additionalInterface}");
            }

            builder.UnIndent();
            builder.OpenScope();
            builder.WriteLine($"{_sourceClassName}();");

            if (_isIDynamic)
            {
                // The IDynamicAnimatedVisualSource implementation has properties that
                // have to be declared in the IDL. The AnimatedVisualInvalidated event does
                // not need to be declared in IDL because it is implied by the class inheriting
                // from IDynamicAnimatedVisualSource.
                builder.WriteLine();
                builder.WriteLine("Boolean IsImageLoadingAsynchronous;");
                builder.WriteLine("Boolean IsImageLoadingCompleted { get; };");
            }

            builder.CloseScopeWithSemicolon();
            builder.CloseScope();
            return builder.ToString();
        }

        // Generates the .h file contents.
        string GenerateHeaderText()
        {
            // Returns the header text that implements IAnimatedVisualSource if loadedImageSurfacesNodes is null or empty.
            // Otherwise, return the header text that implements IDynamicAnimatedVisualSource.
            var builder = new HeaderBuilder();
            builder.Preamble.WriteLine("#pragma once");
            builder.Preamble.WriteLine(string.Join("\r\n", AutoGeneratedHeaderText));

            // Include the cppwinrt.exe generated header.
            builder.Preamble.WriteLine($"#include \"{_cppwinrtGeneratedFileNameBase}.h\"");

            builder.Preamble.WriteLine();
            builder.Preamble.WriteLine($"namespace winrt::{_s.Namespace(SourceInfo.Namespace)}");
            builder.Preamble.OpenScope();

            WriteSourceDescriptionComments(builder.Preamble);

            builder.Preamble.WriteLine($"namespace implementation");
            builder.Preamble.OpenScope();

            if (SourceInfo.GenerateDependencyObject)
            {
                // TODO - support dependency object generation in cppwinrt
                throw new NotImplementedException();
            }

            builder.Class.Preamble.WriteLine($"class {_sourceClassName}");
            builder.Class.Preamble.Indent();
            builder.Class.Preamble.WriteLine($": public {_sourceClassName}T<{_sourceClassName}>");
            builder.Class.Preamble.UnIndent();
            builder.Class.Preamble.OpenScope();

            WriteHeaderConstants(builder.Class.Public);

            if (SourceInfo.IsThemed)
            {
                WriteThemeHeader(builder);
            }

            if (_isIDynamic)
            {
                WriteIDynamicAnimatedVisualSourceHeaderText(builder);
            }

            WriteTryCreateAnimatedVisualDecl(builder.Class.Public);

            builder.Class.Public.WriteLine();

            WriteFrameCountDecl(builder.Class.Public);

            builder.Class.Public.WriteLine();

            WriteFramerateDecl(builder.Class.Public);

            builder.Class.Public.WriteLine();

            WriteDurationDecl(builder.Class.Public);

            builder.Class.Public.WriteLine();

            WriteFrameToProgressDecl(builder.Class.Public);

            builder.Class.Public.WriteLine();

            WriteMarkersPropertyDecl(builder.Class.Public);

            builder.Class.Public.WriteLine();

            WriteSetColorPropertyDecl(builder.Class.Public);

            builder.Class.Public.WriteLine();

            WriteSetScalarPropertyDecl(builder.Class.Public);

            if (SourceInfo.IsThemed)
            {
                builder.Class.Private.WriteLine();
                builder.Class.Private.WriteLine($"static winrt::Windows::Foundation::Numerics::float4 ColorAsVector4(winrt::Windows::UI::Color color);");
            }

            // Close the ::implementation namespace.
            builder.Postamble.Indent();
            builder.Postamble.CloseScope();
            builder.Postamble.WriteLine();

            // Write the factory_implementation namespace. This allows the class to be activatable.
            builder.Postamble.WriteLine("namespace factory_implementation");
            builder.Postamble.OpenScope();
            builder.Postamble.WriteLine($"struct {_sourceClassName} : {_sourceClassName}T<{_sourceClassName}, implementation::{_sourceClassName}>");
            builder.Postamble.OpenScope();
            builder.Postamble.CloseScopeWithSemicolon();
            builder.Postamble.CloseScope();

            return builder.ToString();
        }

        IEnumerable<string> GetConstructorArguments(IAnimatedVisualInfo info)
        {
            yield return "compositor";

            if (SourceInfo.IsThemed)
            {
                yield return SourceInfo.ThemePropertiesFieldName;
            }

            foreach (var loadedImageSurfaceNode in info.LoadedImageSurfaceNodes)
            {
                yield return loadedImageSurfaceNode.FieldName;
            }
        }

        static string[] CommaSeparate(IEnumerable<string> args)
        {
            var result = args.ToArray();
            for (var i = 0; i < result.Length - 1; i++)
            {
                result[i] += ",";
            }

            return result;
        }

        static string TypeName(PropertySetValueType propertySetValueType)
            => propertySetValueType switch
            {
                PropertySetValueType.Color => "Color",
                PropertySetValueType.Scalar => "float",
                PropertySetValueType.Vector2 => "float2",
                PropertySetValueType.Vector3 => "float3",
                PropertySetValueType.Vector4 => "float4",
                _ => throw new InvalidOperationException()
            };

        // Write a description of the source as comments.
        void WriteSourceDescriptionComments(CodeBuilder builder) =>
            builder.WritePreformattedCommentLines(GetSourceDescriptionLines());

        void WriteTryCreateAnimatedVisualDecl(CodeBuilder builder)
        {
            builder.WriteLine($"winrt::{_animatedVisualTypeName} TryCreateAnimatedVisual(");
            builder.Indent();
            builder.WriteLine($"winrt::{_wuc}::Compositor const& compositor);");
            builder.UnIndent();
            builder.WriteLine();

            builder.WriteLine($"winrt::{_animatedVisualTypeName} TryCreateAnimatedVisual(");
            builder.Indent();
            builder.WriteLine($"winrt::{_wuc}::Compositor const& compositor,");
            builder.WriteLine($"winrt::Windows::Foundation::IInspectable& diagnostics);");
            builder.UnIndent();
        }

        void WriteThemeHeader(HeaderBuilder builder)
        {
            // Add a field to hold the theme property set.
            builder.Class.Private.WriteLine($"winrt::{_wuc}::CompositionPropertySet {SourceInfo.ThemePropertiesFieldName}{{ nullptr }};");

            // Add fields for each of the theme properties.
            foreach (var prop in SourceInfo.SourceMetadata.PropertyBindings)
            {
                if (SourceInfo.GenerateDependencyObject)
                {
                    builder.Class.Private.WriteLine($"static {_winUINamespace}::Xaml::DependencyProperty^ _{prop.BindingName}Property;");
                    builder.Class.Private.WriteLine($"static void On{prop.BindingName}Changed({_winUINamespace}::Xaml::DependencyObject d, {_winUINamespace}::Xaml::DependencyPropertyChangedEventArgs e);");
                }
                else
                {
                    var exposedTypeName = QualifiedTypeName(prop.ExposedType);

                    var initialValue = prop.ExposedType switch
                    {
                        PropertySetValueType.Color => _s.ColorArgs((WinCompData.Wui.Color)prop.DefaultValue),
                        PropertySetValueType.Scalar => _s.Float((float)prop.DefaultValue),
                        PropertySetValueType.Vector2 => _s.Vector2Args((Vector2)prop.DefaultValue),
                        PropertySetValueType.Vector3 => _s.Vector3Args((Vector3)prop.DefaultValue),
                        PropertySetValueType.Vector4 => _s.Vector4Args((Vector4)prop.DefaultValue),
                        _ => throw new InvalidOperationException(),
                    };

                    WriteInitializedField(builder.Class.Private, exposedTypeName, $"_theme{prop.BindingName}", _s.VariableInitialization(initialValue));
                }
            }

            // EnsureThemeProperties is private.
            builder.Class.Private.WriteLine($"winrt::{_wuc}::CompositionPropertySet EnsureThemeProperties(winrt::{_wuc}::Compositor compositor);");

            // Write properties declarations for each themed property.
            foreach (var prop in SourceInfo.SourceMetadata.PropertyBindings)
            {
                builder.Class.Public.WriteLine();
                builder.Class.Public.WriteLine($"{QualifiedTypeName(prop.ExposedType)} {prop.BindingName}();");
                builder.Class.Public.WriteLine($"void {prop.BindingName}({QualifiedTypeName(prop.ExposedType)} value);");
            }

            builder.Class.Public.WriteLine();
        }

        void WriteThemePropertyImpls(CodeBuilder builder)
        {
            var propertyBindings = SourceInfo.SourceMetadata.PropertyBindings;

            if (propertyBindings.Any(pb => pb.ExposedType == PropertySetValueType.Color))
            {
                // Write the helper for converting a color to a vector 4.
                builder.WriteLine($"float4 {_sourceClassName}::ColorAsVector4(Color color)");
                builder.OpenScope();
                builder.WriteLine("return { static_cast<float>(color.R), static_cast<float>(color.G), static_cast<float>(color.B), static_cast<float>(color.A) };");
                builder.CloseScope();
                builder.WriteLine();
            }

            builder.WriteLine($"CompositionPropertySet {_sourceClassName}::EnsureThemeProperties(Compositor compositor)");
            builder.OpenScope();
            builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} == nullptr)");
            builder.OpenScope();
            builder.WriteLine($"{SourceInfo.ThemePropertiesFieldName} = compositor.CreatePropertySet();");

            // Initialize the values in the property set.
            foreach (var prop in propertyBindings)
            {
                WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop);
            }

            builder.CloseScope();
            builder.WriteLine();
            builder.WriteLine($"return {SourceInfo.ThemePropertiesFieldName};");
            builder.CloseScope();
            builder.WriteLine();

            // Write property implementations for each theme property.
            foreach (var prop in propertyBindings)
            {
                // Write the getter. This just reads the values out of the backing field.
                builder.WriteLine($"{TypeName(prop.ExposedType)} {_sourceClassName}::{prop.BindingName}()");
                builder.OpenScope();
                builder.WriteLine($"return _theme{prop.BindingName};");
                builder.CloseScope();
                builder.WriteLine();

                // Write the setter. This saves to the backing field, and updates the theme property
                // set if one has been created.
                builder.WriteLine($"void {_sourceClassName}::{prop.BindingName}({TypeName(prop.ExposedType)} value)");
                builder.OpenScope();
                builder.WriteLine($"_theme{prop.BindingName} = value;");
                builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)");
                builder.OpenScope();
                WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop);
                builder.CloseScope();
                builder.CloseScope();
                builder.WriteLine();
            }
        }

        void WriteIDynamicAnimatedVisualSourceHeaderText(HeaderBuilder builder)
        {
            var pub = builder.Class.Public;
            var priv = builder.Class.Private;

            priv.WriteLine($"const int c_loadedImageSurfaceCount = {SourceInfo.LoadedImageSurfaces.Distinct().Count()};");
            priv.WriteLine("int m_loadCompletedEventCount{};");
            priv.WriteLine("bool m_isImageLoadingAsynchronous{};");
            priv.WriteLine("bool m_isImageLoadingCompleted{};");
            priv.WriteLine("bool m_isImageLoadingStarted{};");
            priv.WriteLine("bool m_isTryCreateAnimatedVisualCalled{};");
            priv.WriteLine("winrt::event<Windows::Foundation::TypedEventHandler<");
            priv.Indent();
            priv.WriteLine("Microsoft::UI::Xaml::Controls::IDynamicAnimatedVisualSource,");
            priv.WriteLine("Windows::Foundation::IInspectable>> m_IDynamicAnimatedVisualSourceEvent{};");
            priv.UnIndent();
            priv.WriteLine($"winrt::event<{_winUINamespace}::Xaml::Data::PropertyChangedEventHandler> m_PropertyChanged{{}};");

            foreach (var n in SourceInfo.LoadedImageSurfaces)
            {
                priv.WriteLine($"winrt::{_winUINamespace}::Xaml::Media::{n.TypeName} {n.FieldName}{{ nullptr }};");
            }

            priv.WriteLine("void EnsureImageLoadingStarted();");
            priv.WriteLine("void HandleLoadCompleted(");
            priv.Indent();
            priv.WriteLine($"winrt::{_winUINamespace}::Xaml::Media::LoadedImageSurface sender,");
            priv.WriteLine($"winrt::{_winUINamespace}::Xaml::Media::LoadedImageSourceLoadCompletedEventArgs e);");
            priv.UnIndent();

            // INotifyPropertyChanged implementation.
            pub.WriteLine($"winrt::event_token PropertyChanged(winrt::{_winUINamespace}::Xaml::Data::PropertyChangedEventHandler const& handler);");
            pub.WriteLine("void PropertyChanged(winrt::event_token const& token) noexcept;");

            // IDynamicAnimatedVisualSource implementation.
            pub.WriteLine();
            pub.WriteLine("winrt::event_token AnimatedVisualInvalidated(");
            pub.Indent();
            pub.WriteLine("winrt::Windows::Foundation::TypedEventHandler<Microsoft::UI::Xaml::Controls::IDynamicAnimatedVisualSource, IInspectable> const& handler);");
            pub.UnIndent();
            pub.WriteLine("void AnimatedVisualInvalidated(winrt::event_token const& token) noexcept;");

            pub.WriteLine();
            pub.WriteSummaryComment("If this property is set to true, <see cref=\"TryCreateAnimatedVisual\"/> will" +
                " return null until all images have loaded. When all images have loaded, <see cref=\"TryCreateAnimatedVisual\"/>" +
                " will return the AnimatedVisual. Once <see cref=\"TryCreateAnimatedVisual\"/> is called," +
                " changes made to this property will be ignored. Default value is false.");
            pub.WriteLine("bool IsImageLoadingAsynchronous();");

            pub.WriteLine("void IsImageLoadingAsynchronous(bool value);");
            pub.WriteLine();

            pub.WriteSummaryComment("Returns true if all images have finished loading.");
            pub.WriteLine("bool IsImageLoadingCompleted();");
            pub.WriteLine();
        }

        /// <inheritdoc/>
        protected override void WriteAnimatedVisualStart(
            CodeBuilder builder,
            IAnimatedVisualInfo info)
        {
            // Start writing the instantiator.
            builder.WriteLine($"class {info.ClassName} : public winrt::implements<{info.ClassName},");
            builder.Indent();
            builder.Indent();

            if (info.ImplementCreateAndDestroyMethods)
            {
                builder.WriteLine($"winrt::{_animatedVisualTypeName2},");
            }

            builder.WriteLine($"winrt::{_animatedVisualTypeName},");

            builder.WriteLine($"IClosable>");
            builder.UnIndent();
            builder.UnIndent();
            builder.OpenScope();

            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // D2D factory field.
                builder.WriteLine("winrt::com_ptr<ID2D1Factory> _d2dFactory{ nullptr };");
            }
        }

        void AddUsingsForTypeAliases(CodeBuilder builder)
        {
            // Add usings for type aliases.
            if (SourceInfo.WinUIVersion.Major >= 3)
            {
                builder.WriteLine($"using Color = winrt::Windows::UI::Color;");
                builder.WriteLine($"using CompositionPropertySet = winrt::Microsoft::UI::Composition::CompositionPropertySet;");
            }

            builder.WriteLine($"using TimeSpan = winrt::Windows::Foundation::TimeSpan;");
        }

        void WriteHeaderConstants(CodeBuilder builder)
        {
            // Add any internal constants. There is no "internal" in cppwinrt, so these
            // will become public.
            foreach (var c in SourceInfo.InternalConstants)
            {
                builder.WriteComment(c.Description);
                switch (c.Type)
                {
                    case ConstantType.Color:
                        builder.WriteLine($"static inline const winrt::Windows::UI::Color {c.Name}{_s.Color((WinCompData.Wui.Color)c.Value)};");
                        break;
                    case ConstantType.Int64:
                        builder.WriteLine($"static constexpr int64_t {c.Name}{{ {_s.Int64((long)c.Value)} }};");
                        break;
                    case ConstantType.Float:
                        builder.WriteLine($"static constexpr float {c.Name}{{ {_s.Float((float)c.Value)} }};");
                        break;
                    default:
                        throw new InvalidOperationException();
                }

                builder.WriteLine();
            }
        }

        /// <inheritdoc/>
        // Called by the base class to write the start of the cpp file (i.e. everything up to the body of the Instantiator class).
        protected override void WriteImplementationFileStart(CodeBuilder builder)
        {
            builder.WriteLine("#include \"pch.h\"");
            builder.WriteLine($"#include \"{_headerFileName}\"");

            // {_cppwinrtGeneratedFileNameBase}.cpp is needed for UWP, but is not usually generated by
            // "cppwinrt for WIN32 apps. The file is related to how cppwinrt makes types activatable.
            // If the file exists, we'll include it.
            WriteIncludeFileIfExists(builder, $"{_cppwinrtGeneratedFileNameBase}.cpp");
            builder.WriteLine("#include <winrt/Windows.Foundation.Metadata.h>");
            builder.WriteLine("#include <winrt/Windows.Foundation.Collections.h>");

            if (SourceInfo.WinUIVersion.Major >= 3)
            {
                builder.WriteLine("#include <winrt/Microsoft.UI.Composition.h>");
            }
            else
            {
                builder.WriteLine("#include <winrt/Windows.UI.Composition.h>");
            }

            if (_isIDynamic)
            {
                builder.WriteLine("#include <winrt/Windows.UI.Xaml.Media.h>");

                // Images that load from embedded arrays need Windows.Store.Streams.h.
                if (SourceInfo.LoadedImageSurfaces.Any(lis =>
                    lis.LoadedImageSurfaceType == LoadedImageSurface.LoadedImageSurfaceType.FromStream))
                {
                    builder.WriteLine("#include <winrt/Windows.Storage.Streams.h>");
                }
            }

            if (SourceInfo.UsesCanvas ||
                SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // D2D
                builder.WriteLine("#include \"d2d1.h\"");
                builder.WriteLine("#include <d2d1_1.h>");
                builder.WriteLine("#include <d2d1helper.h>");
                builder.WriteLine("#include <Windows.Graphics.Interop.h>");

                // Interop
                // BUILD_WINDOWS is defined if the code is being built as part of a Microsoft internal
                // Windows build. In that case the types in the Windows.Graphics.Effects.Interop.h file will
                // be in the Windows::Graphics::Effects namespace.
                //
                // Otherwise, the code is being built normally and the types will be in the
                // ::ABI::Windows::Graphics::Effects namespace.
                //
                // To work around this inconsistency, when BUILD_WINDOWS is defined, we wrap the include
                // of Windows.Graphics.Effects.Interop.h in the ABI namespace so that the types in that file
                // will always be in the ::ABI::Windows::Graphics::Effects namespace. And in our
                // generated code we always refer to the types in that file using the ABI:: prefix.
                builder.WriteLine("#ifdef BUILD_WINDOWS");
                builder.WriteLine("namespace ABI");
                builder.WriteLine("{");
                builder.WriteLine("#include <Windows.Graphics.Effects.Interop.h>");
                builder.WriteLine("}");
                builder.WriteLine("#else");
                builder.WriteLine("#include <Windows.Graphics.Effects.Interop.h>");
                builder.WriteLine("#endif");
                builder.WriteLine("#include <winrt/Windows.Graphics.Effects.h>");
            }

            if (SourceInfo.UsesStreams)
            {
                builder.WriteLine("#include <iostream>");
            }

            if (SourceInfo.UsesCompositeEffect)
            {
                // The CompsiteEffect class requires std::vector.
                builder.WriteLine("#include <vector>");
            }

            if (SourceInfo.UsesStreams)
            {
                // Embedded images uses std::array.
                builder.WriteLine("#include <array>");
            }

            builder.WriteLine();

            var namespaces = new CppNamespaceListBuilder();

            namespaces.Add("winrt::Windows::Foundation");
            namespaces.Add("winrt::Windows::Foundation::Numerics");
            namespaces.Add($"winrt::{_winUINamespace}");
            namespaces.Add($"winrt::{_wuc}");
            namespaces.Add("winrt::Windows::Graphics");
            namespaces.Add(Muxc);

            if (SourceInfo.UsesNamespaceWindowsUIXamlMedia)
            {
                namespaces.Add($"winrt::{_winUINamespace}::Xaml::Media");
            }

            if (SourceInfo.UsesStreams)
            {
                namespaces.Add("winrt::Windows::Storage::Streams");
            }

            if (SourceInfo.GenerateDependencyObject)
            {
                namespaces.Add($"{_winUINamespace}::Xaml");
            }

            builder.WriteCodeBuilder(namespaces.ToCodeBuilder());

            AddUsingsForTypeAliases(builder);

            builder.WriteLine();

            builder.WriteLine($"namespace winrt::{_s.Namespace(SourceInfo.Namespace)}::implementation");
            builder.OpenScope();

            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // Write CanvasGeometry to allow it's use in function definitions.
                builder.WriteLine(CanvasGeometryClass);
            }

            if (SourceInfo.UsesCompositeEffect)
            {
                // Write the composite effect class that will allow the use
                // of this effect without win2d.
                builder.WriteLine(CompositeEffectClass);
            }

            if (SourceInfo.UsesGaussianBlurEffect)
            {
                // Write the Gaussian blur effect class that will allow the use
                // of this effect without win2d.
                builder.WriteLine(GaussianBlurEffectClass);
            }
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryCombinationFactory(CodeBuilder builder, CanvasGeometry.Combination obj, string typeName, string fieldName)
        {
            builder.WriteLine("winrt::com_ptr<ID2D1Geometry>geoA{ nullptr };");
            builder.WriteLine("winrt::com_ptr<ID2D1Geometry>geoB{ nullptr };");
            builder.WriteLine($"{CallFactoryFor(obj.A)}->GetGeometry(geoA.put());");
            builder.WriteLine($"{CallFactoryFor(obj.B)}->GetGeometry(geoB.put());");
            builder.WriteLine("winrt::com_ptr<ID2D1PathGeometry> path;");
            builder.WriteLine("winrt::check_hresult(_d2dFactory->CreatePathGeometry(path.put()));");
            builder.WriteLine("winrt::com_ptr<ID2D1GeometrySink> sink{ nullptr };");
            builder.WriteLine("winrt::check_hresult(path->Open(sink.put()));");
            builder.WriteLine($"winrt::check_hresult(geoA->CombineWithGeometry(");
            builder.Indent();
            builder.WriteLine($"geoB.get(),");
            builder.WriteLine($"{_s.CanvasGeometryCombine(obj.CombineMode)},");
            builder.WriteLine($"{_s.Matrix3x2(obj.Matrix)},");
            builder.WriteLine($"sink.get()));");
            builder.UnIndent();
            builder.WriteLine("winrt::check_hresult(sink->Close());");
            builder.WriteLine($"auto result = {FieldAssignment(fieldName)}winrt::make_self<CanvasGeometry>(path);");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryEllipseFactory(CodeBuilder builder, CanvasGeometry.Ellipse obj, string typeName, string fieldName)
        {
            builder.WriteLine("winrt::com_ptr<ID2D1EllipseGeometry> ellipse{ nullptr };");
            builder.WriteLine("winrt::check_hresult(_d2dFactory->CreateEllipseGeometry(");
            builder.Indent();
            builder.WriteLine($"D2D1::Ellipse({{{_s.Float(obj.X)},{_s.Float(obj.Y)}}}, {_s.Float(obj.RadiusX)}, {_s.Float(obj.RadiusY)}),");
            builder.WriteLine("ellipse.put()));");
            builder.UnIndent();
            builder.WriteLine($"auto result = {FieldAssignment(fieldName)}winrt::make_self<CanvasGeometry>(ellipse);");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryRoundedRectangleFactory(CodeBuilder builder, CanvasGeometry.RoundedRectangle obj, string typeName, string fieldName)
        {
            builder.WriteLine("winrt::com_ptr<ID2D1RoundedRectangleGeometry> rect{ nullptr };");
            builder.WriteLine("winrt::check_hresult(_d2dFactory->CreateRoundedRectangleGeometry(");
            builder.Indent();
            builder.WriteLine($"D2D1::RoundedRect({{{_s.Float(obj.X)},{_s.Float(obj.Y)}}}, {_s.Float(obj.RadiusX)}, {_s.Float(obj.RadiusY)}),");
            builder.WriteLine("rect.put()));");
            builder.UnIndent();
            builder.WriteLine($"auto result = {FieldAssignment(fieldName)}winrt::make_self<CanvasGeometry>(rect);");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryTransformedGeometryFactory(CodeBuilder builder, CanvasGeometry.TransformedGeometry obj, string typeName, string fieldName)
        {
            builder.WriteLine("winrt::com_ptr<ID2D1Geometry>geoA{ nullptr };");
            builder.WriteLine($"D2D1_MATRIX_3X2_F transformMatrix{_s.Matrix3x2(obj.TransformMatrix)};");
            builder.WriteLine($"{CallFactoryFor(obj.SourceGeometry)}->GetGeometry(geoA.put());");
            builder.WriteLine("winrt::com_ptr<ID2D1TransformedGeometry> transformed{ nullptr };");
            builder.WriteLine("winrt::check_hresult(_d2dFactory->CreateTransformedGeometry(geoA.get(), transformMatrix, transformed.put()));");
            builder.WriteLine($"auto result = {FieldAssignment(fieldName)}winrt::make_self<CanvasGeometry>(transformed);");
        }

        /// <inheritdoc/>
        protected override string WriteCompositeEffectFactory(CodeBuilder builder, CompositeEffect effect)
        {
            var effectVariable = "compositeEffect";
            builder.WriteLine($"auto {effectVariable} = winrt::make_self<CompositeEffect>();");
            builder.WriteLine($"{effectVariable}->Mode({_s.CanvasCompositeMode(effect.Mode)});");
            foreach (var source in effect.Sources)
            {
                builder.WriteLine($"{effectVariable}->AddSource(CompositionEffectSourceParameter(L\"{source.Name}\"));");
            }

            return $"*{effectVariable}";
        }

        protected override string WriteGaussianBlurEffectFactory(CodeBuilder builder, GaussianBlurEffect effect)
        {
            var effectVariable = "gaussianBlurEffect";
            builder.WriteLine($"auto {effectVariable} = winrt::make_self<GaussianBlurEffect>();");
            builder.WriteLine($"{effectVariable}->BlurAmount({_s.Float(effect.BlurAmount)});");

            builder.WriteLine($"{effectVariable}->Source(CompositionEffectSourceParameter(L\"{effect.Sources.First().Name}\"));");

            return $"*{effectVariable}";
        }

        /// <inheritdoc/>
        protected override void WriteByteArrayField(CodeBuilder builder, string fieldName, IReadOnlyList<byte> bytes)
        {
            builder.WriteLine($"static const std::array<byte, {bytes.Count}> {fieldName}");
            builder.OpenScope();
            builder.WriteByteArrayLiteral(bytes, maximumColumns: 115);
            builder.UnIndent();
            builder.WriteLine("};");
        }

        /// <inheritdoc/>
        // Called by the base class to write the end of the file (i.e. everything after the body of the AnimatedVisual class).
        protected override void WriteImplementationFileEnd(CodeBuilder builder)
        {
            builder.WriteLine();

            // Generate the methods that create and get the theme property set.
            if (SourceInfo.IsThemed)
            {
                WriteThemePropertyImpls(builder);
            }

            // Generate the overload of TryCreateAnimatedVisual that doesn't have diagnostics;
            builder.WriteLine($"winrt::{_animatedVisualTypeName} {_sourceClassName}::TryCreateAnimatedVisual(");
            builder.Indent();
            builder.WriteLine("Compositor const& compositor)");
            builder.UnIndent();
            builder.OpenScope();
            builder.WriteLine("IInspectable diagnostics = nullptr;");
            builder.WriteLine("return TryCreateAnimatedVisual(compositor, diagnostics);");
            builder.CloseScope();
            builder.WriteLine();

            // Generate the method that creates an instance of the composition on the IAnimatedVisualSource.
            builder.WriteLine($"winrt::{_animatedVisualTypeName} {_sourceClassName}::TryCreateAnimatedVisual(");
            builder.Indent();
            builder.WriteLine("Compositor const& compositor,");
            builder.WriteLine("IInspectable& diagnostics)");

            builder.UnIndent();
            builder.OpenScope();

            if (SourceInfo.IsThemed)
            {
                builder.WriteLine("const auto _ = EnsureThemeProperties(compositor);");
            }

            if (_isIDynamic)
            {
                WriteIDynamicAnimatedVisualSource(builder);
            }
            else
            {
                WriteIAnimatedVisualSource(builder);
            }

            builder.WriteLine();

            WriteFrameCountImpl(builder);

            builder.WriteLine();

            WriteFramerateImpl(builder);

            builder.WriteLine();

            WriteDurationImpl(builder);

            builder.WriteLine();

            WriteFrameToProgressImpl(builder);

            builder.WriteLine();

            WriteMarkersPropertyImpl(builder);

            builder.WriteLine();

            WriteSetColorPropertyImpl(builder);

            builder.WriteLine();

            WriteSetScalarPropertyImpl(builder);

            // Close the namespace.
            builder.UnIndent();
            builder.WriteLine("} // end namespace");
        }

        /// <summary>
        /// Generates the FrameCount property declaration.
        /// </summary>
        void WriteFrameCountDecl(CodeBuilder builder)
        {
            builder.WriteComment("Gets the number of frames in the animation.");
            builder.WriteLine($"double FrameCount();");
        }

        /// <summary>
        /// Generates the FrameCount property implementation.
        /// </summary>
        void WriteFrameCountImpl(CodeBuilder builder)
        {
            builder.WriteLine($"double {_sourceClassName}::FrameCount()");
            builder.OpenScope();
            builder.WriteLine($"return {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.Frames)};");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the Framerate property declaration.
        /// </summary>
        void WriteFramerateDecl(CodeBuilder builder)
        {
            builder.WriteComment("Gets the framerate of the animation.");
            builder.WriteLine($"double Framerate();");
        }

        /// <summary>
        /// Generates the Framerate property implementation.
        /// </summary>
        void WriteFramerateImpl(CodeBuilder builder)
        {
            builder.WriteLine($"double {_sourceClassName}::Framerate()");
            builder.OpenScope();
            builder.WriteLine($"return {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.FPS)};");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the Framerate property declaration.
        /// </summary>
        void WriteDurationDecl(CodeBuilder builder)
        {
            builder.WriteComment("Gets the duration of the animation.");
            builder.WriteLine($"winrt::Windows::Foundation::TimeSpan Duration();");
        }

        /// <summary>
        /// Generates the Duration property implementation.
        /// </summary>
        void WriteDurationImpl(CodeBuilder builder)
        {
            builder.WriteLine($"TimeSpan {_sourceClassName}::Duration()");
            builder.OpenScope();
            builder.WriteLine($"return {_s.TimeSpan(SourceInfo.SourceMetadata.LottieMetadata.Duration.Time)};");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the FrameToProgress(...) declaration.
        /// </summary>
        void WriteFrameToProgressDecl(CodeBuilder builder)
        {
            builder.WriteComment("Converts a zero-based frame number to the corresponding progress value denoting the start of the frame.");
            builder.WriteLine($"double FrameToProgress(double frameNumber);");
        }

        /// <summary>
        /// Generates the FrameToProgress(...) implementation.
        /// </summary>
        void WriteFrameToProgressImpl(CodeBuilder builder)
        {
            builder.WriteLine($"double {_sourceClassName}::FrameToProgress(double frameNumber)");
            builder.OpenScope();
            builder.WriteLine($"return frameNumber / {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.Frames)};");
            builder.CloseScope();
        }

        /// <summary>
        /// Generates the Markers property declaration.
        /// </summary>
        void WriteMarkersPropertyDecl(CodeBuilder builder)
        {
            builder.WriteComment("Returns a map from marker names to corresponding progress values.");
            builder.WriteLine("winrt::Windows::Foundation::Collections::IMapView<hstring, double> Markers();");
        }

        /// <summary>
        /// Generates the Markers property implementation.
        /// </summary>
        void WriteMarkersPropertyImpl(CodeBuilder builder)
        {
            builder.WriteLine($"winrt::Windows::Foundation::Collections::IMapView<hstring, double> {_sourceClassName}::Markers()");
            builder.OpenScope();
            builder.WriteLine("return winrt::single_threaded_map<winrt::hstring, double>(");
            builder.Indent();
            builder.WriteLine("std::map<winrt::hstring, double>");

            builder.OpenScope();
            foreach (var marker in SourceInfo.Markers)
            {
                builder.WriteLine($"{{ {_s.String(marker.Name)}, {_s.Double(marker.StartProgress)} }},");
            }

            builder.CloseScope();

            builder.UnIndent();
            builder.WriteLine(").GetView();");
            builder.CloseScope();
        }

        void WriteSetColorPropertyDecl(CodeBuilder builder)
        {
            builder.WriteComment("Sets the color property with the given name, or does nothing if no such property exists.");
            builder.WriteLine("void SetColorProperty(hstring const& propertyName, winrt::Windows::UI::Color value);");
        }

        void WriteSetScalarPropertyDecl(CodeBuilder builder)
        {
            builder.WriteComment("Sets the scalar property with the given name, or does nothing if no such property exists.");
            builder.WriteLine("void SetScalarProperty(hstring const& propertyName, double value);");
        }

        void WriteSetColorPropertyImpl(CodeBuilder builder) =>
            WriteSetPropertyImpl(builder, PropertySetValueType.Color, "Color");

        void WriteSetScalarPropertyImpl(CodeBuilder builder) =>
            WriteSetPropertyImpl(builder, PropertySetValueType.Scalar, "double");

        void WriteSetPropertyImpl(CodeBuilder builder, PropertySetValueType propertyType, string typeName)
        {
            var properties = SourceInfo.SourceMetadata.PropertyBindings.Where(p => p.ExposedType == propertyType).ToArray();

            if (properties.Length == 0)
            {
                // There are no properties. The method doesn't need to do anything.
                builder.WriteLine($"void {_sourceClassName}::Set{propertyType}Property(hstring const&, {typeName})");
                builder.OpenScope();
                builder.CloseScope();
            }
            else
            {
                var propertySetTypeName = PropertySetValueTypeName(properties[0].ActualType);
                var valueInitializer = properties[0].ExposedType == PropertySetValueType.Color ? "ColorAsVector4(value)" : "value";

                builder.WriteLine($"void {_sourceClassName}::Set{propertyType}Property(hstring const& propertyName, {typeName} value)");
                builder.OpenScope();

                var firstSeen = false;
                foreach (var prop in properties)
                {
                    // If the propertyName is a known name, save it into its backing field.
                    builder.WriteLine($"{(firstSeen ? "else " : string.Empty)}if (propertyName == {_s.String(prop.BindingName)})");
                    firstSeen = true;
                    builder.OpenScope();
                    builder.WriteLine($"_theme{prop.BindingName} = value;");
                    builder.CloseScope();
                }

                builder.WriteLine("else");
                builder.OpenScope();

                // Ignore the name if it doesn't refer to a known property.
                builder.WriteLine("return;");
                builder.CloseScope();
                builder.WriteLine();

                // Update the CompositionPropertySet if it has been created.
                builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)");
                builder.OpenScope();

                builder.WriteLine($"{SourceInfo.ThemePropertiesFieldName}.Insert{propertySetTypeName}(propertyName, {valueInitializer});");
                builder.CloseScope();
                builder.CloseScope();
            }
        }

        /// <summary>
        /// Generate the body of the TryCreateAnimatedVisual() method for a composition that does not contain LoadedImageSurfaces.
        /// </summary>
        void WriteIAnimatedVisualSource(CodeBuilder builder)
        {
            builder.WriteLine("diagnostics = nullptr;");

            // Check the runtime version and instantiate the highest compatible IAnimatedVisual class.
            WriteInstantiateHighestCompatibleAnimatedVisual(builder, SourceInfo.AnimatedVisualInfos);

            builder.CloseScope();
        }

        void WriteInstantiateHighestCompatibleAnimatedVisual(
            CodeBuilder builder,
            IReadOnlyList<IAnimatedVisualInfo> animatedVisualInfos)
        {
            // WinUI3 doesn't ever do a version check. It's up to the user to make sure
            // the version they're using is compatible.
            if (SourceInfo.WinUIVersion.Major >= 3)
            {
                var info = animatedVisualInfos.First();
                builder.WriteBreakableLine($"auto result = winrt::make<{info.ClassName}>(", CommaSeparate(GetConstructorArguments(info)), ");");
                if (info.ImplementCreateAndDestroyMethods)
                {
                    builder.WriteLine($"result.{CreateAnimationsMethod}();");
                }

                builder.WriteLine("return result;");
            }
            else
            {
                foreach (var info in animatedVisualInfos.OrderByDescending(avi => avi.RequiredUapVersion))
                {
                    builder.WriteLine($"if ({info.ClassName}::IsRuntimeCompatible())");
                    builder.OpenScope();
                    builder.WriteBreakableLine($"auto result = winrt::make<{info.ClassName}>(", CommaSeparate(GetConstructorArguments(info)), ");");
                    if (info.ImplementCreateAndDestroyMethods)
                    {
                        builder.WriteLine($"result.{CreateAnimationsMethod}();");
                    }

                    builder.WriteLine("return result;");
                    builder.CloseScope();
                }

                builder.WriteLine();
                builder.WriteLine("return nullptr;");
            }
        }

        /// <summary>
        /// Generate the body of the TryCreateAnimatedVisual() method for a composition that contains LoadedImageSurfaces.
        /// </summary>
        void WriteIDynamicAnimatedVisualSource(CodeBuilder builder)
        {
            builder.WriteLine("m_isTryCreateAnimatedVisualCalled = true;");
            builder.WriteLine("diagnostics = nullptr;");
            builder.WriteLine();

            // Check whether the runtime will support the lowest UAP version required.
            var animatedVisualInfos = SourceInfo.AnimatedVisualInfos.OrderByDescending(avi => avi.RequiredUapVersion).ToArray();

            // WinUI3 doesn't ever do a version check. It's up to the user to make sure
            // the version they're using is compatible.
            if (SourceInfo.WinUIVersion.Major < 3)
            {
                builder.WriteLine($"if (!{animatedVisualInfos[^1].ClassName}::IsRuntimeCompatible())");
                builder.OpenScope();
                builder.WriteLine("return nullptr;");
                builder.CloseScope();
            }

            builder.WriteLine();
            builder.WriteLine("EnsureImageLoadingStarted();");
            builder.WriteLine();
            builder.WriteLine("if (m_isImageLoadingAsynchronous && m_loadCompletedEventCount != c_loadedImageSurfaceCount)");
            builder.OpenScope();
            builder.WriteLine("return nullptr;");
            builder.CloseScope();

            // Check the runtime version and instantiate the highest compatible IAnimatedVisual class.
            WriteInstantiateHighestCompatibleAnimatedVisual(builder, animatedVisualInfos);

            builder.CloseScope();
            builder.WriteLine();

            // Generate the get() and set() methods of IsImageLoadingAsynchronous property.
            WriteSimplePropertyGetterImpl(builder, "IsImageLoadingAsynchronous", "m_isImageLoadingAsynchronous", "bool");
            builder.WriteLine($"void {_sourceClassName}::IsImageLoadingAsynchronous(bool value)");
            builder.OpenScope();
            builder.WriteLine("if (!m_isTryCreateAnimatedVisualCalled && m_isImageLoadingAsynchronous != value)");
            builder.OpenScope();
            builder.WriteLine("m_isImageLoadingAsynchronous = value;");
            builder.WriteLine($"m_PropertyChanged(*this, {_winUINamespace}::Xaml::Data::PropertyChangedEventArgs(L\"IsImageLoadingAsynchronous\"));");
            builder.CloseScope();
            builder.CloseScope();
            builder.WriteLine();

            // Generate the get() method of IsImageLoadingCompleted.
            WriteSimplePropertyGetterImpl(builder, "IsImageLoadingCompleted", "m_isImageLoadingCompleted", "bool");

            // Generate the method that loads all the LoadedImageSurfaces.
            WriteEnsureImageLoadingStarted(builder);

            // Generate the method that handles the LoadCompleted event of the LoadedImageSurface objects.
            WriteHandleLoadCompleted(builder);

            // Generate the PropertyChanged event implementation.
            WriteEventImpl(
                builder,
                "PropertyChanged",
                $"{_winUINamespace}::Xaml::Data::PropertyChangedEventHandler",
                "m_PropertyChanged");

            // Generate the AnimatedVisualInvalidated event implementation.
            WriteEventImpl(
                builder,
                "AnimatedVisualInvalidated",
                "TypedEventHandler<Microsoft::UI::Xaml::Controls::IDynamicAnimatedVisualSource, IInspectable>",
                "m_IDynamicAnimatedVisualSourceEvent");
        }

        void WriteEventImpl(CodeBuilder builder, string eventName, string handlerTypeName, string backingEventName)
        {
            builder.WriteLine($"winrt::event_token {_sourceClassName}::{eventName}({handlerTypeName} const& handler)");
            builder.OpenScope();
            builder.WriteLine($"return {backingEventName}.add(handler);");
            builder.CloseScope();
            builder.WriteLine();

            builder.WriteLine($"void {_sourceClassName}::{eventName}(winrt::event_token const& token) noexcept");
            builder.OpenScope();
            builder.WriteLine($"{backingEventName}.remove(token);");
            builder.CloseScope();
            builder.WriteLine();
        }

        void WriteSimplePropertyGetterImpl(CodeBuilder builder, string methodName, string backingFieldName, string typeName)
        {
            builder.WriteLine($"{typeName} {_sourceClassName}::{methodName}()");
            builder.OpenScope();
            builder.WriteLine($"return {backingFieldName};");
            builder.CloseScope();
            builder.WriteLine();
        }

        void WriteEnsureImageLoadingStarted(CodeBuilder builder)
        {
            builder.WriteLine($"void {_sourceClassName}::EnsureImageLoadingStarted()");
            builder.OpenScope();
            builder.WriteLine("if (!m_isImageLoadingStarted)");
            builder.OpenScope();
            builder.WriteLine("m_isImageLoadingStarted = true;");
            builder.WriteLine($"TypedEventHandler<LoadedImageSurface, LoadedImageSourceLoadCompletedEventArgs> eventHandler{{ this, &{_sourceClassName}::HandleLoadCompleted }};");

            foreach (var n in SourceInfo.LoadedImageSurfaces)
            {
                var imageMemberName = n.FieldName;
                switch (n.LoadedImageSurfaceType)
                {
                    case LoadedImageSurface.LoadedImageSurfaceType.FromStream:
                        builder.OpenScope();
                        builder.WriteLine("InMemoryRandomAccessStream stream{};");
                        builder.WriteLine("DataWriter dataWriter{ stream.GetOutputStreamAt(0) };");
                        builder.WriteLine($"dataWriter.WriteBytes({n.BytesFieldName});");
                        builder.WriteLine("dataWriter.StoreAsync();");
                        builder.WriteLine("dataWriter.FlushAsync();");
                        builder.WriteLine($"{imageMemberName} = LoadedImageSurface::StartLoadFromStream(stream);");
                        builder.WriteLine($"{imageMemberName}.LoadCompleted(eventHandler);");
                        builder.CloseScope();
                        break;
                    case LoadedImageSurface.LoadedImageSurfaceType.FromUri:
                        builder.WriteComment(n.Comment);
                        builder.WriteLine($"{imageMemberName} = LoadedImageSurface::StartLoadFromUri(Uri(L\"{n.ImageUri.AbsoluteUri}\"));");
                        builder.WriteLine($"{imageMemberName}.LoadCompleted(eventHandler);");
                        break;
                    default:
                        throw new InvalidOperationException();
                }
            }

            builder.CloseScope();
            builder.CloseScope();
            builder.WriteLine();
        }

        void WriteHandleLoadCompleted(CodeBuilder builder)
        {
            // Called for each LoadCompleted callback for each image. This code does
            // not care whether the loading was successful, just that it completed.
            builder.WriteLine($"void {_sourceClassName}::HandleLoadCompleted(LoadedImageSurface sender, LoadedImageSourceLoadCompletedEventArgs e)");
            builder.OpenScope();
            builder.WriteLine("m_loadCompletedEventCount++;");
            builder.WriteLine();
            builder.WriteLine("if (m_loadCompletedEventCount == c_loadedImageSurfaceCount)");
            builder.OpenScope();
            builder.WriteLine("m_isImageLoadingCompleted = true;");
            builder.WriteLine($"m_PropertyChanged(*this, {_winUINamespace}::Xaml::Data::PropertyChangedEventArgs(L\"IsImageLoadingCompleted\"));");

            // If asynchronouse image loading is enabled notify via IDynamicAnimatedVisualSource that
            // the previous result is now invalidated.
            builder.WriteLine("if (m_isImageLoadingAsynchronous)");
            builder.OpenScope();
            builder.WriteLine("m_IDynamicAnimatedVisualSourceEvent(*this, nullptr);");
            builder.CloseScope();
            builder.CloseScope();
            builder.CloseScope();
            builder.WriteLine();
        }

        void WriteIsRuntimeCompatibleMethod(CodeBuilder builder, IAnimatedVisualInfo info)
        {
            // WinUI3 doesn't ever do a version check. It's up to the user to make sure
            // the version they're using is compatible.
            if (SourceInfo.WinUIVersion.Major < 3)
            {
                // Write the IsRuntimeCompatible static method.
                builder.WriteLine("static bool IsRuntimeCompatible()");
                builder.OpenScope();
                builder.WriteLine($"return winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L\"Windows.Foundation.UniversalApiContract\", {info.RequiredUapVersion});");
                builder.CloseScope();
            }
        }

        /// <inheritdoc/>
        // Called by the base class to write the end of the AnimatedVisual class.
        protected override void WriteAnimatedVisualEnd(
            CodeBuilder builder,
            IAnimatedVisualInfo info,
            CodeBuilder createAnimations,
            CodeBuilder destroyAnimations)
        {
            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                // Utility method for D2D geometries.
                builder.WriteLine("static IGeometrySource2D CanvasGeometryToIGeometrySource2D(winrt::com_ptr<CanvasGeometry> geo)");
                builder.OpenScope();
                builder.WriteLine("return geo.as<IGeometrySource2D>();");
                builder.CloseScope();
                builder.WriteLine();
            }

            // Write the constructor for the AnimatedVisual class.
            builder.UnIndent();
            builder.WriteLine("public:");
            builder.Indent();

            // Constructor.
            builder.WriteBreakableLine($"{info.ClassName}(", CommaSeparate(GetConstructorParameters(info)), ")");
            builder.Indent();

            // Initializer list.
            builder.WriteLine(": _c{compositor}");
            if (SourceInfo.IsThemed)
            {
                builder.WriteLine($", {SourceInfo.ThemePropertiesFieldName}{{themeProperties}}");
            }

            // Initialize the image surfaces.
            foreach (var n in info.LoadedImageSurfaceNodes)
            {
                builder.WriteLine($", {n.FieldName}({_s.CamelCase(n.Name)})");
            }

            // Instantiate the reusable ExpressionAnimation.
            builder.WriteLine($", {SourceInfo.ReusableExpressionAnimationFieldName}(compositor.CreateExpressionAnimation())");

            builder.UnIndent();

            builder.OpenScope();
            if (SourceInfo.UsesCanvasEffects ||
                SourceInfo.UsesCanvasGeometry)
            {
                builder.WriteLine("winrt::check_hresult(D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, _d2dFactory.put()));");
            }

            // Instantiate the root. This will cause the whole Visual tree to be built and animations started.
            builder.WriteLine("const auto _ = Root();");
            builder.CloseScope();

            builder.WriteLine();
            builder.WriteLine("void Close()");
            builder.OpenScope();
            builder.WriteLine("if (_root)");
            builder.OpenScope();
            builder.WriteLine("_root.Close();");
            builder.CloseScope();
            builder.CloseScope();

            // Write the members on IAnimatedVisual.
            builder.WriteLine();
            {
                var propertyImplBuilder = new CodeBuilder();
                propertyImplBuilder.WriteLine($"return TimeSpan{{ {SourceInfo.DurationTicksFieldName} }};");
                WritePropertyImpl(builder, "TimeSpan", "Duration", propertyImplBuilder);
            }

            {
                var propertyImplBuilder = new CodeBuilder();
                propertyImplBuilder.WriteLine("return _root;");
                WritePropertyImpl(builder, nameof(Visual), "RootVisual", propertyImplBuilder);
            }

            {
                var propertyImplBuilder = new CodeBuilder();
                propertyImplBuilder.WriteLine($"return {_s.Vector2(SourceInfo.CompositionDeclaredSize)};");
                WritePropertyImpl(builder, "float2", "Size", propertyImplBuilder);
            }

            if (info.ImplementCreateAndDestroyMethods)
            {
                builder.WriteLine($"void {CreateAnimationsMethod}()");
                builder.OpenScope();
                builder.WriteCodeBuilder(createAnimations);
                builder.CloseScope();
                builder.WriteLine();

                builder.WriteLine($"void {DestroyAnimationsMethod}()");
                builder.OpenScope();
                builder.WriteCodeBuilder(destroyAnimations);
                builder.CloseScope();
                builder.WriteLine();
            }

            WriteIsRuntimeCompatibleMethod(builder, info);

            // Close the scope for the instantiator class.
            builder.CloseScopeWithSemicolon();
        }

        void WritePropertyImpl(
            CodeBuilder builder,
            string returnType,
            string propertyName,
            CodeBuilder getImplementation)
        {
            builder.WriteLine($"{returnType} {propertyName}() const");
            builder.OpenScope();
            builder.WriteCodeBuilder(getImplementation);
            builder.CloseScope();
            builder.WriteLine();
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryPathFactory(CodeBuilder builder, CanvasGeometry.Path obj, string typeName, string fieldName)
        {
            // D2D Setup
            builder.WriteLine("winrt::com_ptr<ID2D1PathGeometry> path{ nullptr };");
            builder.WriteLine("winrt::check_hresult(_d2dFactory->CreatePathGeometry(path.put()));");
            builder.WriteLine("winrt::com_ptr<ID2D1GeometrySink> sink{ nullptr };");
            builder.WriteLine("winrt::check_hresult(path->Open(sink.put()));");

            if (obj.FilledRegionDetermination != CanvasFilledRegionDetermination.Alternate)
            {
                builder.WriteLine($"sink->SetFillMode({_s.FilledRegionDetermination(obj.FilledRegionDetermination)});");
            }

            foreach (var command in obj.Commands)
            {
                switch (command.Type)
                {
                    case CanvasPathBuilder.CommandType.BeginFigure:
                        // Assume D2D1_FIGURE_BEGIN_FILLED
                        builder.WriteLine($"sink->BeginFigure({_s.Vector2(((CanvasPathBuilder.Command.BeginFigure)command).StartPoint)}, D2D1_FIGURE_BEGIN_FILLED);");
                        break;
                    case CanvasPathBuilder.CommandType.EndFigure:
                        builder.WriteLine($"sink->EndFigure({_s.CanvasFigureLoop(((CanvasPathBuilder.Command.EndFigure)command).FigureLoop)});");
                        break;
                    case CanvasPathBuilder.CommandType.AddLine:
                        builder.WriteLine($"sink->AddLine({_s.Vector2(((CanvasPathBuilder.Command.AddLine)command).EndPoint)});");
                        break;
                    case CanvasPathBuilder.CommandType.AddCubicBezier:
                        var cb = (CanvasPathBuilder.Command.AddCubicBezier)command;
                        builder.WriteLine($"sink->AddBezier({{ {_s.Vector2(cb.ControlPoint1)}, {_s.Vector2(cb.ControlPoint2)}, {_s.Vector2(cb.EndPoint)} }});");
                        break;
                    default:
                        throw new InvalidOperationException();
                }
            }

            builder.WriteLine("winrt::check_hresult(sink->Close());");
            builder.WriteLine($"auto result = {FieldAssignment(fieldName)}winrt::make_self<CanvasGeometry>(path);");
        }

        /// <inheritdoc/>
        protected override void WriteCanvasGeometryGroupFactory(CodeBuilder builder, CanvasGeometry.Group obj, string typeName, string fieldName)
        {
            builder.WriteLine($"winrt::com_ptr<ID2D1Geometry> geometries[{obj.Geometries.Length}]");
            builder.OpenScope();
            for (var i = 0; i < obj.Geometries.Length; i++)
            {
                var geometry = obj.Geometries[i];
                builder.WriteLine($"{CallFactoryFor(geometry)}.get()->Geometry(),");
            }

            builder.CloseScopeWithSemicolon();
            builder.WriteLine("winrt::com_ptr<ID2D1GeometryGroup> group{ nullptr };");
            builder.WriteLine("winrt::check_hresult(_d2dFactory->CreateGeometryGroup(");
            builder.Indent();
            builder.WriteLine($"{_s.FilledRegionDetermination(obj.FilledRegionDetermination)},");
            builder.WriteLine("(ID2D1Geometry**)(&geometries),");
            builder.WriteLine($"{obj.Geometries.Length},");
            builder.WriteLine("group.put()));");
            builder.UnIndent();
            builder.WriteLine($"auto result = {FieldAssignment(fieldName)}winrt::make_self<CanvasGeometry>(group);");
        }

        // Writes code that is only included if the given file exists.
        static void WriteIncludeFileIfExists(CodeBuilder builder, string fileName)
            => WriteIfFileExists(
                builder,
                fileName,
                () => builder.WriteLine($"#include \"{fileName}\""));

        // Writes code that is only imported if the given file exists.
        static void WriteImportFileIfExists(CodeBuilder builder, string fileName)
            => WriteIfFileExists(
                builder,
                fileName,
                () => builder.WriteLine($"import \"{fileName}\";"));

        // Writes code that is only compiled if the given file exists.
        static void WriteIfFileExists(CodeBuilder builder, string fileName, Action action)
        {
            builder.WriteLine($"#if __has_include (\"{fileName}\")");
            action();
            builder.WriteLine("#endif");
        }

        string QualifiedTypeName(PropertySetValueType propertySetValueType)
            => propertySetValueType switch
            {
                PropertySetValueType.Color => $"winrt::Windows::UI::Color",
                _ => TypeName(propertySetValueType),
            };

        IEnumerable<string> GetConstructorParameters(IAnimatedVisualInfo info)
        {
            yield return "Compositor compositor";

            if (SourceInfo.IsThemed)
            {
                yield return "CompositionPropertySet themeProperties";
            }

            foreach (var loadedImageSurfaceNode in info.LoadedImageSurfaceNodes)
            {
                yield return $"{loadedImageSurfaceNode.TypeName} {_s.CamelCase(loadedImageSurfaceNode.Name)}";
            }
        }

        sealed class ClassBuilder
        {
            internal CodeBuilder Preamble { get; } = new CodeBuilder();

            internal CodeBuilder Private { get; } = new CodeBuilder();

            internal CodeBuilder Public { get; } = new CodeBuilder();

            internal CodeBuilder ToCodeBuilder()
            {
                var result = new CodeBuilder();
                result.WriteCodeBuilder(Preamble);
                result.Indent();
                result.WriteCodeBuilder(Private);
                result.UnIndent();
                result.WriteLine("public:");
                result.Indent();
                result.WriteCodeBuilder(Public);
                result.CloseScopeWithSemicolon();
                return result;
            }

            public override string ToString() => ToCodeBuilder().ToString();
        }

        sealed class HeaderBuilder
        {
            // Everything up to and including the opening of the namespace
            internal CodeBuilder Preamble { get; } = new CodeBuilder();

            internal ClassBuilder Class { get; } = new ClassBuilder();

            internal CodeBuilder Postamble { get; } = new CodeBuilder();

            internal CodeBuilder ToCodeBuilder()
            {
                var result = new CodeBuilder();
                result.WriteCodeBuilder(Preamble);
                result.Indent();
                result.Indent();
                result.WriteCodeBuilder(Class.ToCodeBuilder());
                result.UnIndent();
                result.WriteCodeBuilder(Postamble);
                result.CloseScope();
                return result;
            }

            public override string ToString() => ToCodeBuilder().ToString();
        }

        static string CanvasGeometryClass =>
@"class CanvasGeometry : public winrt::implements<CanvasGeometry,
        IGeometrySource2D,
        ::ABI::Windows::Graphics::IGeometrySource2DInterop>
    {
        winrt::com_ptr<ID2D1Geometry> _geometry{ nullptr };

    public:
        CanvasGeometry(winrt::com_ptr<ID2D1Geometry> geometry)
            : _geometry{ geometry }
        { }

        // IGeometrySource2D.
        winrt::com_ptr<ID2D1Geometry> Geometry() { return _geometry; }

        // IGeometrySource2DInterop.
        IFACEMETHODIMP GetGeometry(ID2D1Geometry** value) override
        {
            _geometry.copy_to(value);
            return S_OK;
        }

        // IGeometrySource2DInterop.
        IFACEMETHODIMP TryGetGeometryUsingFactory(ID2D1Factory*, ID2D1Geometry**) override
        {
            return E_NOTIMPL;
        }
    };";

        static string CompositeEffectClass =>
@"
    enum class CanvasComposite : int
    {
        SourceOver = 0,
        DestinationOver = 1,
        SourceIn = 2,
        DestinationIn = 3,
        SourceOut = 4,
        DestinationOut = 5,
        SourceAtop = 6,
        DestinationAtop = 7,
        Xor = 8,
        Add = 9,
        Copy = 10,
        BoundedCopy = 11,
        MaskInvert = 12,
    };

    // This class is a substitute for the Microsoft::Graphics::Canvas::Effects::CompositeEffect
    // class so that composite effects can be used with 
    // Windows::UI::Composition::CompositionEffectBrush without requiring Win2d.
    class CompositeEffect : public winrt::implements<CompositeEffect,
        winrt::Windows::Graphics::Effects::IGraphicsEffect,
        winrt::Windows::Graphics::Effects::IGraphicsEffectSource,
        ::ABI::Windows::Graphics::Effects::IGraphicsEffectD2D1Interop>
    {
        winrt::hstring m_name{};
        CanvasComposite m_mode{};
        std::vector<winrt::Windows::Graphics::Effects::IGraphicsEffectSource> m_sources{};

    public:
        void Mode(CanvasComposite mode) { m_mode = mode; }
        CanvasComposite Mode(){ return m_mode; }

        void AddSource(winrt::Windows::Graphics::Effects::IGraphicsEffectSource source)
        {
            m_sources.emplace_back(source);
        }

        // IGraphicsEffect.
        void Name(winrt::hstring name) { m_name = name; }
        winrt::hstring Name() { return m_name; }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetEffectId(GUID* id) override
        {
            if (id != nullptr)
            {
                // CLSID_D2D1Composite.
                *id = { 0x48fc9f51, 0xf6ac, 0x48f1, { 0x8b, 0x58, 0x3b, 0x28, 0xac, 0x46, 0xf7, 0x6d } };
            }

            return S_OK;
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetSourceCount(UINT* count) override
        {
            if (count != nullptr)
            {
                *count = static_cast<UINT>(m_sources.size());
            }

            return S_OK;
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetSource(
            UINT index,
            ::ABI::Windows::Graphics::Effects::IGraphicsEffectSource** source) override
        {
            if (index >= m_sources.size() ||
                source == nullptr)
            {
                return E_INVALIDARG;
            }

            m_sources.at(index).as<::ABI::Windows::Graphics::Effects::IGraphicsEffectSource>().copy_to(source);

            return S_OK;
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetPropertyCount(UINT* count) override { *count = 1; return S_OK; }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetProperty(
            UINT index,
            ::ABI::Windows::Foundation::IPropertyValue** value) override
        {
            switch (index)
            {
            case D2D1_COMPOSITE_PROP_MODE:
                winrt::Windows::Foundation::PropertyValue::CreateUInt32(
                    static_cast<uint32_t>(m_mode)).as<::ABI::Windows::Foundation::IPropertyValue>().copy_to(value);
                return S_OK;
            default:
                *value = nullptr;
                return E_INVALIDARG;
            }
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetNamedPropertyMapping(
            LPCWSTR,
            UINT*,
            ::ABI::Windows::Graphics::Effects::GRAPHICS_EFFECT_PROPERTY_MAPPING*) override
        {
            return E_INVALIDARG;
        }
    };
";

        static string GaussianBlurEffectClass =>
@"
    // This class is a substitute for the Microsoft::Graphics::Canvas::Effects::GaussianBlurEffect
    // class so that blur effects can be used with 
    // Windows::UI::Composition::CompositionEffectBrush without requiring Win2d.
    class GaussianBlurEffect : public winrt::implements<GaussianBlurEffect,
        winrt::Windows::Graphics::Effects::IGraphicsEffect,
        winrt::Windows::Graphics::Effects::IGraphicsEffectSource,
        ::ABI::Windows::Graphics::Effects::IGraphicsEffectD2D1Interop>
    {
        float m_blurAmount = 3.0f;
        winrt::hstring m_name{};
        winrt::Windows::Graphics::Effects::IGraphicsEffectSource m_source{};

    public:
        void BlurAmount(float amount) { m_blurAmount = amount; }

        void Source(winrt::Windows::Graphics::Effects::IGraphicsEffectSource source) { m_source = source; }

        // IGraphicsEffect.
        void Name(winrt::hstring name) { m_name = name; }
        winrt::hstring Name() { return m_name; }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetEffectId(GUID* id) override
        {
            if (id != nullptr)
            {
                // CLSID_D2D1GaussianBlur.
                *id = { 0x1feb6d69, 0x2fe6, 0x4ac9, { 0x8c, 0x58, 0x1d, 0x7f, 0x93, 0xe7, 0xa6, 0xa5 } };
            }

            return S_OK;
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetSourceCount(UINT* count) override
        {
            if (count != nullptr)
            {
                *count = 1;
            }

            return S_OK;
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetSource(
            UINT index,
            ::ABI::Windows::Graphics::Effects::IGraphicsEffectSource** source) override
        {
            if (index != 0 ||
                source == nullptr)
            {
                return E_INVALIDARG;
            }

            m_source.as<::ABI::Windows::Graphics::Effects::IGraphicsEffectSource>().copy_to(source);

            return S_OK;
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetPropertyCount(UINT* count) override { *count = 3; return S_OK; }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetProperty(
            UINT index,
            ::ABI::Windows::Foundation::IPropertyValue** value) override
        {
            switch (index)
            {
            case D2D1_GAUSSIANBLUR_PROP_BORDER_MODE:
                winrt::Windows::Foundation::PropertyValue::CreateUInt32(
                    0).as<::ABI::Windows::Foundation::IPropertyValue>().copy_to(value);
            return S_OK;

            case D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION:
                winrt::Windows::Foundation::PropertyValue::CreateUInt32(
                    1).as<::ABI::Windows::Foundation::IPropertyValue>().copy_to(value);
            return S_OK;

            case D2D1_GAUSSIANBLUR_PROP_STANDARD_DEVIATION:
                winrt::Windows::Foundation::PropertyValue::CreateSingle(
                    m_blurAmount).as<::ABI::Windows::Foundation::IPropertyValue>().copy_to(value);
            return S_OK;

            default:
                *value = nullptr;
                return E_INVALIDARG;
            }
        }

        // IGraphicsEffectD2D1Interop.
        IFACEMETHODIMP GetNamedPropertyMapping(
            LPCWSTR,
            UINT*,
            ::ABI::Windows::Graphics::Effects::GRAPHICS_EFFECT_PROPERTY_MAPPING*) override
        {
            return E_INVALIDARG;
        }
    };
";
    }
}
